#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdint.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>
#include <linux/fb.h>
#include "bmp.h"

struct lcd_info
{
    int fd;
    int width;
    int height;
    int bpp;
    char *fb_mem; // 显存的入口
};

struct bmp_info
{
    int width;
    int height;
    int bpp;
    int size;
    char *rgb; // 显存的入口
};

void init_lcd(struct lcd_info *lcdinfo)
{
    lcdinfo->fd = open("/dev/fb0", O_RDWR);
    if(lcdinfo->fd == -1)
    {
        perror("打开LCD设备失败");
        exit(0);
    }

    struct fb_var_screeninfo vinfo;
    struct fb_fix_screeninfo finfo;
    // 获取LCD硬件参数
    ioctl(lcdinfo->fd, FBIOGET_VSCREENINFO, &vinfo);
    ioctl(lcdinfo->fd, FBIOGET_FSCREENINFO, &finfo);

    lcdinfo->width = vinfo.xres;    
    lcdinfo->height= vinfo.yres;    
    lcdinfo->bpp   = vinfo.bits_per_pixel;    
    int screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel/8;

    // 将映射内存设置为 800 * 480
    lcdinfo->fb_mem = mmap(NULL, screensize, PROT_READ|PROT_WRITE,
                           MAP_SHARED, lcdinfo->fd, 0);
    if(lcdinfo->fb_mem == MAP_FAILED)
    {
        perror("内存映射失败");
        exit(0);
    }
}

void load_bmp(const char *bmpfile, struct bmp_info *bmpinfo)
{
    FILE *fp = fopen(bmpfile, "r");

    struct bitmap_header head;
    struct bitmap_info   info;
    struct rgb_quad      quad;
    fread(&head, sizeof(head), 1, fp);
    fread(&info, sizeof(info), 1, fp);
    if(info.compression)
        fread(&quad, sizeof(quad), 1, fp);

    bmpinfo->width = info.width;
    bmpinfo->height= info.height;
    bmpinfo->bpp   = info.bit_count;
    printf("图片尺寸：%d × %d （色深%d比特）\n", bmpinfo->width,
                                                bmpinfo->height,
                                                bmpinfo->bpp);

    bmpinfo->size = info.width*info.height*info.bit_count/8;
    bmpinfo->rgb = calloc(1, bmpinfo->size);

    // 加载RGB数据
    int n, total=0, rgbsize=bmpinfo->size;
    while(rgbsize > 0)
    {
        n = fread(bmpinfo->rgb+total, 1, rgbsize, fp); // n就是实际读到的字节数

        rgbsize -= n;
        total   += n;
    }
    printf("total: %d\n", total);
    
    fclose(fp);
}

int main(int argc, char const *argv[]) // ./a.out xxx.bmp
{
    // 1. 准备好LCD
    struct lcd_info lcdinfo;
    init_lcd(&lcdinfo);

    // 2. 读取BMP图片的格式头，获取其尺寸信息
    // 3. 读取BMP图片的RGB数据
    struct bmp_info bmpinfo;
    load_bmp(argv[1], &bmpinfo);

    // 计算无效字节
    int linesize = bmpinfo.width*bmpinfo.bpp/8;
    int pad = (4-linesize%4) % 4; // 0 ~ 3

    // 取最底一行的指针
    char *rgb = bmpinfo.rgb + (linesize+pad)*(bmpinfo.height-1);

    // 4. 按照BMP的特性将RGB显示出来
    int w, h;
    for(w=0, h=0; h<bmpinfo.height && h<lcdinfo.height; h++)
    {
        unsigned lcd_offset = h * lcdinfo.width*4;
        
        // 显示一行
        for(w=0; w<bmpinfo.width && w<lcdinfo.width; w++)
            memcpy(lcdinfo.fb_mem + w*4 + lcd_offset, rgb + w*3 , 3);

        rgb -= (linesize + pad);
    }

    // 5. 释放相关资源

    return 0;
}
